#include "RoStanzas.h"


RoStanza::RoStanza(const RoStanza &other):
    QObject(),
    super(other)
{
}


RoStanza& RoStanza::operator =(const RoStanza& rhs)
{
    super::operator=(rhs);
    return *this;
}


RoStanza::RoStanza()
{
}


RoStanza::RoStanza(QString tagName, QDomDocument source):
    super(source)
{
    if ( source.isNull() )
        appendChild(createElement(tagName));
}


QString RoStanza::from() const
{
    return documentElement().attribute("from");
}


QString RoStanza::to() const
{
    return documentElement().attribute("to");
}


QString RoStanza::type() const
{
    return documentElement().attribute("type");
}


RoStanzaId RoStanza::id() const
{
    return documentElement().attribute("id");
}


QDomElement RoStanza::body()
{
    return documentElement().firstChildElement();
}


QString RoStanza::attribute(const QString &name, const QString &defValue) const
{
    return documentElement().attribute(name, defValue);
}


RoStanza & RoStanza::setAttribute(const QString &name, const QString &value)
{
    documentElement().setAttribute(name, value);
    return *this;
}


QString RoStanza::bodyAttribute(const QString &name, const QString &defValue) const
{
    return documentElement().firstChildElement().attribute(name, defValue);
}


RoStanza & RoStanza::setBodyAttribute(const QString &name, const QString &value)
{
    documentElement().firstChildElement().setAttribute(name, value);
    return *this;
}


QString RoStanza::bodyText() const
{
    return documentElement().firstChildElement().text();
}


RoStanza & RoStanza::setBodyText(const QString &text)
{
    QDomElement body = documentElement().firstChildElement();

    // Ищем первого текстового потомка
    QDomNodeList children = body.childNodes();
    QDomNode textNode;
    for ( uint i = 0; i < children.length(); ++i )
    {
        if ( children.at(i).isText() )
        {
            textNode = children.at(i);
            break; // found!
        }
    }

    // если не нашли текстового потомка, создаём
    // если нашли текстового потомка, заменяем
    if ( textNode.isNull() )
        body.appendChild(createTextNode(text));
    else
        body.replaceChild(createTextNode(text), textNode);

    return *this;
}


const int RoErrorStanza::ErrorTypesCount = _ErrorTypesCountPlus1 - 1;

QMap<RoErrorStanza::ErrorType, QString> RoErrorStanza::TagNames;
QMap<RoErrorStanza::ErrorType, QString> RoErrorStanza::DefaultDescriptions;


RoErrorStanza::RoErrorStanza(QDomDocument source):
    super("error", source)
{
    init();
}


RoErrorStanza::RoErrorStanza(ErrorType errorType):
    super("error")
{
    init();

    Q_ASSERT( 1 <= errorType and errorType <= ErrorTypesCount and // must be in range 1 to n,
              errorType != _CriticalErrors ); // excluding dividers

    QDomElement errorElem = createElement(TagNames[errorType]);
    {
        QDomElement descriptionElem = createElement("description");
        {
            descriptionElem.appendChild(createTextNode(DefaultDescriptions[errorType].toUtf8()));
        }
        errorElem.appendChild(descriptionElem);
    }
    documentElement().appendChild(errorElem);
}


RoErrorStanza::ErrorType RoErrorStanza::errorType() const
{
    return TagNames.key(documentElement().firstChildElement().tagName());
}


void RoErrorStanza::init()
{
    if ( TagNames.isEmpty() )
    {
        TagNames.insert(BadFormat, "bad-format");
        TagNames.insert(Conflict, "conflict");
        TagNames.insert(ConnectionTimeout, "connection-timeout");
        TagNames.insert(InternalError, "internal-error");
        TagNames.insert(NotAuthorized, "not-authorized");
        TagNames.insert(ResourceConstrained, "resource-constrained");
        TagNames.insert(SystemShutdown, "system-shutdown");
        TagNames.insert(UnsupportedVersion, "unsupported-version");
        TagNames.insert(XmlNotWellFormed, "xml-not-well-formed");

        TagNames.insert(ImproperAddressing, "improper-addressing");
        TagNames.insert(InvalidFrom, "invalid-from");
        TagNames.insert(InvalidId, "invalid-id");
        TagNames.insert(RestrictedXml, "restricted-xml");
        TagNames.insert(UnsupportedStanzaType, "unsupported-stanza-type");

        DefaultDescriptions.insert(BadFormat, "Получен XML-код, который не может быть обработан."); // Данную ошибку следует конкретизировать как <restricted-xml/> или <xml-not-well-formed/>.
        DefaultDescriptions.insert(Conflict, "Маршрутизатор закрывает соединение из-за того, что было открыто новое, мешающее старому.");
        DefaultDescriptions.insert(ConnectionTimeout, "Компонент достаточно долго ничего не писал в поток.");
        DefaultDescriptions.insert(InternalError, "Компонент неправильно настроен или что-то иное мешает ему правильно обработать данные.");
        DefaultDescriptions.insert(NotAuthorized, "Компонент пытается послать какие-то данные до открытия потока.");
        DefaultDescriptions.insert(ResourceConstrained, "Компоненту не хватает системных ресурсов для обработки поступающих данных.");
        DefaultDescriptions.insert(SystemShutdown, "Сервер выключается и закрывает все потоки.");
        DefaultDescriptions.insert(UnsupportedVersion, "Версия протокола не поддерживается. Я знаю только 1.0.");
        DefaultDescriptions.insert(XmlNotWellFormed, "Переданные данные не удовлетворяют стандарту XML.");

        DefaultDescriptions.insert(ImproperAddressing, "У строфы отсутствует атрибут from или to, в то время как он нужен.");
        DefaultDescriptions.insert(InvalidFrom, "Значение атрибута from не соответствует тем, которые подключены на этом соединении.");
        DefaultDescriptions.insert(InvalidId, "Значение атрибута id или rid отсутствует или неправильное.");
        DefaultDescriptions.insert(RestrictedXml, "Компонент пытается использовать запрещённые возможности XML: комментарии, инструкции обработки, DTD, ссылки на сущности или неэкранированные символы.");
        DefaultDescriptions.insert(UnsupportedStanzaType, "Компонент отправил элемент, непосредственно вложенный в поток, непонятный компоненту-получателю.");
    }
}


RoInfoStanza::RoInfoStanza(QDomDocument source):
    super("info", source)
{
}


RoInfoStanza::RoInfoStanza(const QString & name):
    super("info")
{
    documentElement().appendChild(createElement(name));
}


QString RoInfoStanza::name() const
{
    return documentElement().firstChildElement().tagName();
}


RoIqStanza::RoIqStanza(QDomDocument source):
    super("iq", source)
{
    init();
}


RoIqStanza RoIqStanza::createRequest(QString to, QString method, QDomDocument argument, QString from)
{
    RoIqStanza stanza;

    QDomElement iq = stanza.documentElement();
    iq.setAttribute("type", "request");

    if ( not from.isEmpty() )
        iq.setAttribute("from", from);
    iq.setAttribute("to", to);
    iq.setAttribute("method", method);

    iq.appendChild(argument.documentElement());

    return stanza;
}


RoIqStanza RoIqStanza::createDatum(QString to, RoStanzaId rid, QDomDocument payload, QString from)
{
    RoIqStanza stanza;

    QDomElement iq = stanza.documentElement();
    iq.setAttribute("type", "datum");

    if ( not from.isEmpty() )
        iq.setAttribute("from", from);
    iq.setAttribute("to", to);
    iq.setAttribute("rid", rid.toString());

    iq.appendChild(payload.documentElement());

    return stanza;
}


RoIqStanza RoIqStanza::createDatum(QString to, RoStanzaId rid, QString payloadName, QString from)
{
    QDomDocument payload;
    payload.appendChild(payload.createElement(payloadName));
    return createDatum(to, rid, payload, from);
}


RoIqStanza RoIqStanza::createResult(QString to, RoStanzaId rid, QDomDocument payload, QString from)
{
    RoIqStanza stanza;

    QDomElement iq = stanza.documentElement();
    iq.setAttribute("type", "result");

    if ( not from.isEmpty() )
        iq.setAttribute("from", from);
    iq.setAttribute("to", to);
    iq.setAttribute("rid", rid.toString());

    iq.appendChild(payload.documentElement());

    return stanza;
}


RoIqStanza RoIqStanza::createResult(QString to, RoStanzaId rid, QString payloadName, QString from)
{
    QDomDocument payload;
    payload.appendChild(payload.createElement(payloadName));
    return createResult(to, rid, payload, from);
}


RoIqStanza RoIqStanza::createError(ErrorType errorType, QString to, RoStanzaId rid, QString from)
{
    Q_ASSERT( 1 <= errorType and errorType <= _ErrorTypesCountPlus1 - 1 ); // must be in range 1 to n

    RoIqStanza stanza;

    QDomElement iq = stanza.documentElement();
    iq.setAttribute("type", "error");

    if ( not from.isEmpty() )
        iq.setAttribute("from", from);
    iq.setAttribute("to", to);
    iq.setAttribute("rid", rid.toString());

    QDomElement errorElem = stanza.createElement(TagNames[errorType]);
    {
        QDomElement descriptionElem = stanza.createElement("description");
        {
            descriptionElem.appendChild(stanza.createTextNode(DefaultDescriptions[errorType].toUtf8()));
        }
        errorElem.appendChild(descriptionElem);
    }
    iq.appendChild(errorElem);

    return stanza;
}


RoIqStanza RoIqStanza::createError(QString errorType, QString to, RoStanzaId rid, QString from)
{
    RoIqStanza stanza = RoIqStanza::createError(UserType, to, rid, from);
    stanza.body().setTagName(errorType);
    return stanza;
}


QString RoIqStanza::method() const
{
    return documentElement().attribute("method");
}


RoStanzaId RoIqStanza::rid() const
{
    return documentElement().attribute("rid");
}


QDomDocument RoIqStanza::argument() const
{
    return payload();
}


QDomDocument RoIqStanza::payload() const
{
    QDomDocument doc = this->cloneNode().toDocument();
    doc.replaceChild( doc.documentElement().firstChildElement(),
                      doc.documentElement() );
    return doc;
}


RoStanza & RoIqStanza::setErrorDescription(QString text)
{
    documentElement() // iq
        .firstChildElement() // an-error
            .firstChildElement("description")
                .firstChild()
                    .setNodeValue(text);
    return *this;
}


QMap<RoIqStanza::ErrorType, QString> RoIqStanza::TagNames;
QMap<RoIqStanza::ErrorType, QString> RoIqStanza::DefaultDescriptions;


void RoIqStanza::init()
{
    if ( TagNames.isEmpty() )
    {
        TagNames.insert(BadParameters, "bad-parameters");
        TagNames.insert(ComponentGone, "component-gone");
        TagNames.insert(InternalError, "internal-error");
        TagNames.insert(MethodUnknown, "method-unknown");
        TagNames.insert(RecipientUnknown, "recipient-unknown");
        TagNames.insert(RidUnknown, "rid-unknown");
        TagNames.insert(SenderUnknown, "sender-unknown");

        TagNames.insert(UserType, "unknown-application-specific-error");

        DefaultDescriptions.insert(BadParameters, "Ошибка в параметрах при обращении к методу.");
        DefaultDescriptions.insert(ComponentGone, "Удалённый компонент ушёл, не завершив диалог. Вероятно, он вышел из строя из-за внутренней ошибки.");
        DefaultDescriptions.insert(InternalError, "Компонент неправильно настроен или что-то иное мешает ему правильно обработать данные.");
        DefaultDescriptions.insert(MethodUnknown, "Неизвестный метод.");
        DefaultDescriptions.insert(RecipientUnknown, "Адресат неизвестен маршрутизатору.");
        DefaultDescriptions.insert(RidUnknown, "В настоящее время компонент не ведёт диалог с таким идентификатором.");
        DefaultDescriptions.insert(SenderUnknown, "В настоящее время компонент не ведёт диалог с другим компонентом с таким именем.");

        DefaultDescriptions.insert(UserType, "Общая ошибка уровня приложения. Если вы это видите, значит, кое-кто не задал описание этой ошибки.");
    }
}
